using System.Collections.Concurrent;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using RabbitMQ.Client;

namespace Aurora.RabbitMq.Provider;

/// <summary>
/// channel pool
/// </summary>
public class ChannelPool : IChannelPool
{
    protected IConnectionPool ConnectionPool { get; }

    protected ConcurrentDictionary<string, ChannelPoolItem> Channels { get; }


    protected bool IsDisposed { get; private set; }

    protected TimeSpan TotalDisposeWaitDuration { get; set; } = TimeSpan.FromSeconds(10);

    public ILogger<ChannelPool> Logger { get; set; }

    public ChannelPool(IConnectionPool connectionPool)
    {
        ConnectionPool = connectionPool;
        Channels = new ConcurrentDictionary<string, ChannelPoolItem>();
        Logger = NullLogger<ChannelPool>.Instance;
    }


    public IChannelAccessor Acquire(string? channelName = null, string? connectionName = null)
    {
        CheckDisposed();

        channelName ??= "";

        var poolItem = Channels.GetOrAdd(channelName,
            _ => new ChannelPoolItem(CreateChannel(channelName, connectionName)));

        poolItem.Acquire();

        return new ChannelAccessor(poolItem.Channel, channelName, () => poolItem.Release());
    }


    protected virtual IModel CreateChannel(string channelName, string? connectionName)
    {
        return ConnectionPool.Get(connectionName).CreateModel();
    }

    protected void CheckDisposed()
    {
        if (IsDisposed)
        {
            throw new ObjectDisposedException(nameof(ChannelPool));
        }
    }

    public void Dispose()
    {
        if (IsDisposed) return;

        IsDisposed = true;

        if (!Channels.Any())
        {
            Logger.LogDebug("Disposed channel pool with no channels in the pool.");
            return;
        }

        var poolDisposeStopwatch = Stopwatch.StartNew();
        Logger.LogInformation($"Disposing channel pool ({Channels.Count} channels).");

        var remainingWaitDuration = TotalDisposeWaitDuration;
        foreach (var poolItem in Channels.Values)
        {
            var poolItemDisposeStopwatch = Stopwatch.StartNew();
            try
            {
                poolItem.WaitIfInUse(remainingWaitDuration);
                poolItem.Dispose();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }

            poolItemDisposeStopwatch.Stop();

            remainingWaitDuration = remainingWaitDuration > poolItemDisposeStopwatch.Elapsed
                ? remainingWaitDuration.Subtract(poolItemDisposeStopwatch.Elapsed)
                : TimeSpan.Zero;
        }


        poolDisposeStopwatch.Stop();

        Logger.LogInformation(
            $"Disposed channel pool ({Channels.Count} channels) in {poolDisposeStopwatch.Elapsed.TotalSeconds:0.00} ms.");

        if (poolDisposeStopwatch.Elapsed.TotalSeconds > 5.0)
        {
            Logger.LogWarning(
                $"Disposing RabbitMQ Channel Pool got time greather than expected: {poolDisposeStopwatch.Elapsed.TotalMilliseconds:0.00} ms.");
        }

        Channels.Clear();
    }
}